package org.moda.functional.collection;

import org.moda.functional.fp.Monad;

import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

public abstract sealed class Stream<A> implements Monad<A> permits Empty, Nodes {
    /**
     * 这个以后想办法移走，或者使用static的
     */
    public static final Empty nil = Empty.of();

    public static <A> Stream<A> of(A... as) {
        if (as.length == 0) {
            return nil;
        } else {
            return new Nodes<A>(as[0], of(Arrays.<A>copyOfRange(as, 1, as.length)));
        }
    }

    public Stream<A> concat(Stream<A> x) {
        return concat(this, x);
    }

    public static <T> Stream<T> concat(Stream<T> a, Stream<T> b) {
        return switch (a) {
            case Empty      __          ->  b;
            case Nodes<T>   x           ->  new Nodes<T>(x.head, concat(x.tail, b));
        };
    }

    @Override
    public <B> Stream<B> flatMap(Function<A, Monad<B>> f) {
         Function<A, Stream<B>> f_ = a -> f.apply(a).<Stream<B>>convert();
        Stream<Stream<B>> r = forYield(this, f_);
        return flat(r);
    }

    private static <A, B> Stream<B> forYield(Stream<A> xs, Function<A, B> f) {
        return switch (xs) {
            case Empty                  __          ->  nil;
            case Nodes<A>               x           ->  new Nodes<B>(f.apply(x.head), forYield(x.tail, f));
        };
    }

    private static <B> Stream<B> flat(Stream<Stream<B>> sx) {
        return switch (sx) {
            case Empty                  __          ->  nil;
            case Nodes<Stream<B>>       x           ->  concat(x.head, flat(x.tail));
        };
    }

    @Override
    public <B> Stream<B> unit(B b) {
        return Stream.<B>of(b);
    }

    public void forEach(Consumer<A> f) {
        forEach(this, f);
    }

    private static <A> void forEach(Stream<A> sx, Consumer<A> f) {
        Function<Nodes<A>, Object> xf = xx -> {
            f.accept(xx.head);
            forEach(xx.tail, f);
            return null;
        };

        Object __ = switch (sx) {
            case Empty                x         ->  null;
            case Nodes<A>             y         ->  xf.apply(y);
        };
    }

    public <B> B fold(B u, BiFunction<A, B, B> f) {
        return switch (this) {
            case Empty ignore       ->  u;
            case Nodes<A> nodes     ->  f.apply(nodes.head, nodes.tail.<B>fold(u, f));
        };
    }
//
//
//    public Boolean exists(Function<A, Boolean> f) {
//        return this.foldRight(false, (e, r) -> f.apply(e) || r);
//    }
}

final class Empty<T> extends Stream<T> {
    private Empty() {

    }
    public static <T> Empty<T> of() {
        return new Empty<T>();
    }
}

final class Nodes<T> extends Stream<T> {
    public final T head;
    public final Stream<T> tail;
    public Nodes(T head, Stream<T> tail) {
        this.head = head;
        this.tail = tail;
    }
}
